package com.harveyspace.aes.social.twitter;

import com.harveyspace.aes.core.utils.HttpUtil;
import com.harveyspace.aes.core.utils.JsonUtil;
import com.harveyspace.aes.social.Oauth;
import com.harveyspace.aes.social.SocialConfig;
import com.harveyspace.aes.social.exception.SocialException;
import com.harveyspace.aes.social.pojo.AccessTokenInfo;
import com.harveyspace.aes.social.pojo.SocialiteUserInfo;
import com.harveyspace.aes.social.utils.HttpClientSSL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * twitter 登录
 * <p>
 * apitwitter.vpgame.com api.twitter.com
 *
 * @Author <a href="harveyer@163.com">harvey</a>
 * @date 2018/2/9
 */
public class OauthTwitter extends Oauth {

    private static final String HMAC_SHA1 = "HmacSHA1";
    private final String TWITTER_BASE_URL = "https://api.twitter.com/";
    private final String TWITTER_AGENT_URL = "https://apitwitter.vpgame.com/";
    private final String REQUEST_TOKEN_URL = TWITTER_AGENT_URL + "oauth/request_token";
    private final String ACCESS_TOKEN_URL = TWITTER_AGENT_URL + "oauth/access_token";
    private final String USER_INFO_URL = TWITTER_AGENT_URL + "1.1/account/verify_credentials.json";
    private final String AUTH_URL = TWITTER_BASE_URL + "oauth/authorize";

    /**
     * @param socialConfig 配置信息
     */
    public OauthTwitter(SocialConfig socialConfig) {
        super(socialConfig);
    }

    /**
     * 获取授权url
     *
     * @return String
     */
    @Override
    public String getAuthorizeUrl(String state) {

        Map<String, String> params = new HashMap<>();
        params.put("oauth_token", getOauthToken(params));

        return this.initParams(AUTH_URL, params);
    }

    /**
     * 获取token 返回的是JSON 解析获取到Token以及OpenId
     *
     * @param code 使用code换取token
     * @return String 返回类型
     * @throws SocialException 获取access token 异常
     */
    @Override
    public AccessTokenInfo getAccessToken(Map<String, String> code) throws SocialException {

        String oauthVerifier = code.get("oauth_verifier");

        Map<String, String> httpParams = new HashMap<>();
        httpParams.put("oauth_verifier", oauthVerifier);

        String authorizationString = generateAuthorizationHeader(
                "post",
                ACCESS_TOKEN_URL,
                httpParams,
                code.get("oauth_token")
        );

        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", authorizationString);

        Map<String, Object> params = new HashMap<>();
        params.put("oauth_verifier", oauthVerifier);

        String result = "";
        Map<String, String> responseMap;
        try {
            result = HttpClientSSL.postForJson(ACCESS_TOKEN_URL, JsonUtil.toJsonString(params), headers);

            responseMap = formatAccessTokenResult(result);

        } catch (Exception e) {

            String errorMessage = String.format("通过 oauth verifier[%s] 获取 access token异常:%s", oauthVerifier, result);
            logger.error(errorMessage, e);
            throw new SocialException(errorMessage);
        }

        if (responseMap.size() == 0) {

            throw new SocialException(String.format("通过 oauth verifier[%s] 获取 access token异常:%s", oauthVerifier, result));
        }

        AccessTokenInfo accessTokenInfo = new AccessTokenInfo();
        accessTokenInfo.setAccessToken(responseMap.get("oauth_token"));
        accessTokenInfo.setOauthTokenSecret(responseMap.get("oauth_token_secret"));
        accessTokenInfo.setOpenId(responseMap.get("user_id"));
        accessTokenInfo.setUnionId(responseMap.get("user_id"));

        return accessTokenInfo;
    }

    @Override
    public SocialiteUserInfo getSocialUserInfo(AccessTokenInfo accessTokenInfo) throws SocialException {

        String oauthToken = accessTokenInfo.getAccessToken();
        String oauthTokenSecret = accessTokenInfo.getOauthTokenSecret();

        if (StringUtils.isBlank(oauthToken) ||
                StringUtils.isBlank(oauthTokenSecret)) {
            throw new SocialException("oauthToken or oauthTokenSecret is Blank!");
        }

        TwitterUserInfo info = getUserInfo(oauthToken, oauthTokenSecret);

        SocialiteUserInfo socialiteUserInfo = new SocialiteUserInfo();
        socialiteUserInfo.setUnionId(accessTokenInfo.getUnionId());
        socialiteUserInfo.setOpenId(accessTokenInfo.getOpenId());

        if (null != info) {
            socialiteUserInfo.setAvatar(info.getProfileImageUrl());
            socialiteUserInfo.setNickName(info.getName());
            socialiteUserInfo.setName(info.getName());
        }

        return socialiteUserInfo;
    }

    private TwitterUserInfo getUserInfo(String oauthToken, String oauthTokenSecret) throws SocialException {

        // 变更为用户的oauth token secret
        socialConfig.setClientTokenSecret(oauthTokenSecret);

        Map<String, String> params = new HashMap<>();
        params.put("include_email", "true");
        params.put("include_entities", "false");
        params.put("skip_status", "true");

        String authorizationString = generateAuthorizationHeader("get", USER_INFO_URL, params, oauthToken);

        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", authorizationString);

        String result = "";
        Map<String, Object> responseMap;
        try {
            result = HttpClientSSL.getString(initParams(USER_INFO_URL, params), headers);

            responseMap = JsonUtil.toMap(result);

        } catch (Exception e) {
            String errorMessage = String.format("通过 oauthToken[%s] 获取用户信息异常:%s", oauthToken, result);
            logger.error(errorMessage, e);
            throw new SocialException(errorMessage);
        }

        if (StringUtils.isNotBlank(MapUtils.getString(responseMap, "errors"))) {

            String errorMessage = MapUtils.getString(responseMap, "errors");

            throw new SocialException(String.format("通过 oauthToken[%s] 获取用户信息异常:%s", oauthToken, errorMessage));
        }

        return JsonUtil.toObject(result, TwitterUserInfo.class);
    }

    private String getOauthToken(Map<String, String> httpParams) {

        String authorizationString = generateAuthorizationHeader(
                "post",
                REQUEST_TOKEN_URL,
                httpParams,
                socialConfig.getClientAccessToken()
        );

        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", authorizationString);

        String oauthToken = null;
        String result = "";

        try {

            result = HttpClientSSL.postForJson(REQUEST_TOKEN_URL, JsonUtil.toJsonString(httpParams), headers);
            Map<String, String> responseMap = parserUrlParams(result);
            oauthToken = responseMap.get("oauth_token");

        } catch (Exception e) {
            logger.error("获取 oauth token 异常", e);
        }

        if (StringUtils.isBlank(oauthToken)) {
            logger.warn(String.format("获取 oauth token 异常：%s", result));
        }

        return oauthToken;
    }

    private String generateAuthorizationHeader(String method, String url, Map<String, String> httpParams, String oauthToken) {

        long timestamp = System.currentTimeMillis() / 1000;

        Map<String, String> headers = new TreeMap<>();

        if (null != socialConfig.getRedirectUri()) {
            headers.put("oauth_callback", socialConfig.getRedirectUri());
        }

        headers.put("oauth_consumer_key", socialConfig.getClientId());
        headers.put("oauth_nonce", String.valueOf(timestamp));
        headers.put("oauth_signature_method", "HMAC-SHA1");
        headers.put("oauth_timestamp", String.valueOf(timestamp));
        headers.put("oauth_token", oauthToken);
        headers.put("oauth_version", "1.0");

        if (null != httpParams) {
            headers.putAll(httpParams);
        }

        url = constructRequestURL(url.replace(TWITTER_AGENT_URL, TWITTER_BASE_URL));

        String queryParameters = joinParameters(headers, "&", false);

        String signature = method.toUpperCase() +
                "&" +
                HttpUtil.getURLEncoderString(url, "utf-8") +
                "&" +
                HttpUtil.getURLEncoderString(queryParameters, "utf-8");

        String oauthSignature = generateSignature(signature);

        headers.put("oauth_signature", HttpUtil.getURLEncoderString(oauthSignature, "utf-8"));

        return "OAuth " + joinParameters(headers, ",", true);
    }

    private String generateSignature(String data) {

        final Base64.Encoder encoder = Base64.getEncoder();

        byte[] byteHMAC = null;
        String oauthSignature = HttpUtil.getURLEncoderString(socialConfig.getClientSecret(), "utf-8")
                + "&"
                + HttpUtil.getURLEncoderString(socialConfig.getClientTokenSecret(), "utf-8");

        try {
            Mac mac = Mac.getInstance(HMAC_SHA1);
            SecretKeySpec spec = new SecretKeySpec(oauthSignature.getBytes(), HMAC_SHA1);
            mac.init(spec);
            byteHMAC = mac.doFinal(data.getBytes());
        } catch (InvalidKeyException ike) {
            logger.error("Failed initialize \"Message Authentication Code\" (MAC)", ike);
        } catch (NoSuchAlgorithmException nsae) {
            logger.error("Failed to get HmacSHA1 \"Message Authentication Code\" (MAC)", nsae);
        }

        return encoder.encodeToString(byteHMAC);
    }

    private String joinParameters(Map<String, String> httpParams, String splitter, boolean quot) {

        StringBuilder buf = new StringBuilder();

        for (Map.Entry<String, String> entry : httpParams.entrySet()) {
            if (buf.length() != 0) {
                if (quot) {
                    buf.append("\"");
                }
                buf.append(splitter);
            }
            buf.append(entry.getKey()).append("=");
            if (quot) {
                buf.append("\"");
            }
            buf.append(entry.getValue());
        }
        if (buf.length() != 0) {
            if (quot) {
                buf.append("\"");
            }
        }
        return buf.toString();
    }

    private String constructRequestURL(String url) {
        int index = url.indexOf("?");
        if (-1 != index) {
            url = url.substring(0, index);
        }
        int slashIndex = url.indexOf("/", 8);
        String baseURL = url.substring(0, slashIndex).toLowerCase();
        int colonIndex = baseURL.indexOf(":", 8);
        if (-1 != colonIndex) {
            // url contains port number
            if (baseURL.startsWith("http://") && baseURL.endsWith(":80")) {
                // http default port 80 MUST be excluded
                baseURL = baseURL.substring(0, colonIndex);
            } else if (baseURL.startsWith("https://") && baseURL.endsWith(":443")) {
                // http default port 443 MUST be excluded
                baseURL = baseURL.substring(0, colonIndex);
            }
        }
        url = baseURL + url.substring(slashIndex);

        return url;
    }

    private Map<String, String> formatAccessTokenResult(String result) {

        String[] resultStrings = result.split("&");

        Map<String, String> resultMap = new HashMap<>();
        if (resultStrings.length > 1) {

            for (String resultString : resultStrings) {

                if (StringUtils.isBlank(resultString)) {
                    continue;
                }

                String[] keys = resultString.split("=");

                if (keys.length == 2) {
                    resultMap.put(keys[0], keys[1]);
                }
            }
        }

        return resultMap;
    }
}
